<template>
  <BaseContextMenu 右键菜单
    ref="base"
    @on-pop="onPop"
    @on-hide="onHide"
    :z-index="10"
    :outside-ignore="['#localedit','#paramedit']">
    <div class='create-node-menu' flex>
      <div w="8" b-r="1px solid #ffffff80" whitespace-nowrap
        v-if="oBus.pendingSlot?.prototype.type === PSlotType.Data">
        <div class="cnButton" bg="#00000000"><span scale-50>新建</span></div>
        <div class="my-action cnButton" bg-amber-5><span scale-50 @click="onNewLocal">变量</span></div>
        <div class="my-action cnButton" bg-blue-5><span scale-50 @click="onNewFunc">函数</span></div>
      </div>
      <div flex flex-col flex-1>
        <input ref="searchInputEl" w-full v-model="searchInput" h-8 lh-8/>

        <div 菜单列表 of-auto flex-1 barz class="color-content">
          <div v-for="(group, index) in menuGroups">
            <div lh-6 px2 c-white :style="{ backgroundImage: getBgByDesc(group.title) }">{{ group.title }}</div>
            <div v-if="group.title === '操作符/纯函数'" flex flex-wrap p2 pb0>
              <div v-for="(item, index) in group.items" @click="instantiateNode(item, oBus.toLocal(position))" px2 py1 mr2 mb2
                cursor-pointer b-rd-1 bg-gray-7 c-white select-none hover-bg-gray-5>
                {{ item.toString() }}
              </div>
            </div>
            <div v-else-if="group.title === '流程'" flex flex-wrap>
              <div v-for="(item, index) in group.items" @click="instantiateNode(item, oBus.toLocal(position))" px1 py1 m2 flex
                items-center justify-center cursor-pointer b-rd-1 bg-gray-7 c-white select-none hover-bg-gray-5>
                <ElIcon>
                  <ElIconShare v-if="item.title === '分支'" />
                  <ElIconRefreshRight v-if="['循环', '迭代', '重复'].includes(item.title)" />
                </ElIcon>
                <div mx1>{{ item.toString() }}</div>
              </div>
            </div>
            <div v-else-if="['函数(项目)', '函数(库)'].includes(group.title)">
              <div v-for="(item, index) in group.items" @click="instantiateNode(item, oBus.toLocal(position))" py1 px2
                cursor-pointer select-none hover-bg="#ffffff40" flex>
                <ElTooltip effect="light" placement="right">
                  <template #content>
                    <div text-size-4 class="convex" p2>
                      <div color-amber-5 flex-shrink-0 mr1 mb1>{{ item.title }}</div>
                      <div mb1>
                        <div c="#ffffff80" b-b="1px solid #ffffff80" mb1>参数</div>
                        <div v-if="item.slots.filter(s => s.direction === 'in' && s.type === PSlotType.Data).length">
                          <div
                            v-for="slot in item.slots.filter(s => s.direction === 'in' && s.type === PSlotType.Data)"
                            mr1>
                            <span c-blue-2 mr>{{ slot.title }}</span>
                            <span c-cyan-5>{{ slot.dataType!.name }}</span>
                          </div>
                        </div>
                        <div v-else text-gray>无</div>
                      </div>
                      <div>
                        <div c="#ffffff80" b-b="1px solid #ffffff80" mb1>返回值</div>
                        <div v-if="item.slots.filter(s => s.direction === 'out' && s.type === PSlotType.Data).length">
                          <div
                            v-for="slot in item.slots.filter(s => s.direction === 'out' && s.type === PSlotType.Data)"
                            mr1>
                            <span c-blue-2 mr>{{ slot.title }}</span>
                            <span c-cyan-5>{{ slot.dataType }}</span>
                          </div>
                        </div>
                        <div v-else text-gray>无</div>
                      </div>

                    </div>
                  </template>
                  <span flex-shrink-0 px4>{{ item.title }}</span>
                </ElTooltip>


              </div>
            </div>
            <div v-else>
              <div v-for="(item, index) in group.items" @click="instantiateNode(item, oBus.toLocal(position))" py1 px4
                cursor-pointer select-none hover-bg="#ffffff40">
                {{ item.toString() }}
              </div>
            </div>

          </div>
        </div>
      </div>
    </div>

  </BaseContextMenu>
</template>

<script setup lang="ts">
import { GetAllNodes } from '~/models/pyBundles';
import { MathPoint } from '~/models/Math/MathPoint';
import { oBus } from '~/models/graph/editorbus';
import { oGraph, Graph } from '~/models/graph/graph';
import { INode } from '~/models/graph/node/instance';
import { PNode } from '~/models/graph/node/prototype';
import { PNodeType } from '~/models/graph/node/type';
import { PSlotType } from "~/models/graph/slot/type";
import { oProject, oStorage } from '~/models/Project';
import type BaseContextMenu from '../BaseContextMenu.vue';

const base = ref<InstanceType<typeof BaseContextMenu> | null>(null)
const position = ref(new MathPoint())

// expose透传
defineExpose({
  show: computed(() => base.value?.show),
  pop: (e: MouseEvent) => { base.value?.pop(e) },
  hide: () => { base.value?.hide() },
})

function onPop(ev: MouseEvent) {
  position.value = new MathPoint(ev.clientX, ev.clientY)
  setTimeout(() => {
    searchInputEl.value?.focus()
  })
}

// const didnotHideCauseEditing = ref(false)
function onHide() {
  searchInput.value = ''
  oBus.value.clearPending()
}

/**
 * 搜索框
 */
const searchInput = ref('')
const searchInputEl = ref(null as null | HTMLElement)

const nodeNameAlias = new Map<string, string>([
  ['Condition', 'If']
])

const nodeFilter = computed(() => {
  return (node: PNode) => {
    return (
      node.name.toLowerCase().includes(searchInput.value)
      || node.title.includes(searchInput.value)
      || nodeNameAlias.get(node.name)?.toLowerCase().includes(searchInput.value)
    )
  }
})

function instantiateNode(proto: PNode, point: MathPoint) {
  console.log(`instantiateNode`, proto, point);
  
  // 创建节点
  const node = new INode(proto, true)
  node.x = point.x
  node.y = point.y
  oGraph.value!.nodesId.push(node.id)

  // 连接节点
  const pending = oBus.value.pendingSlot
  if (pending) {
    // 寻找匹配的槽连接
    // console.log('pending',pending);
    // console.log('node.slotsId',node.slotsId);
    // console.log('store',oStorage.value.slots.map(x => x.id));

    const slot = node.slots.find(s => s.compatibleWith(pending))
    // console.log('slot', slot);

    if (slot) {
      // console.log('slot', slot);
      // console.log('pending', pending);

      pending.connect(slot)

      // 如果连接的不是过程槽，那么尝试帮助连接以下对应节点的过程槽
      // todo
    }
  }

  // 隐藏菜单
  base.value?.hide()

  // 清除pending
  oBus.value.clearPending()
}

interface MenuGroup {
  title: string
  items: PNode[]
  action?: () => void
}
const menuGroups = computed(() => {

  // 输入框筛选节点
  const allNodes = GetAllNodes()
  // console.log('allNodes', allNodes);
  
  const nodeFiltered = allNodes.filter(nodeFilter.value)

  const allGroups: MenuGroup[] = []

  allGroups.push({
    title: '操作符/纯函数',
    items: [
      ...nodeFiltered.filter(n => n.type === PNodeType.Expression),
      ...nodeFiltered.filter(n => n.type === PNodeType.PureFunc),
    ]
  })

  allGroups.push({
    title: '流程',
    items: nodeFiltered.filter(n => n.type === PNodeType.Process)
  },)

  allGroups.push({
    title: '函数(项目)',
    items: oStorage.value!.graphs.filter(x => x !== oGraph.value).map(g => PNode.funcFromGraph(g))
  })

  allGroups.push({
    title: '函数(库)',
    items: nodeFiltered.filter(n => n.type === PNodeType.Function)
  },)

  allGroups.push({
    title: '变量(局部)',
    items: oGraph.value!.locals.map(v => PNode.getterFromTGD(v))
  })

  allGroups.push({
    title: '变量(项目/库)',
    items: [
      ...nodeFiltered.filter(n => n.type === PNodeType.Getter),
      ...oProject.value!.storage.globals.map(v => PNode.getterFromTGD(v)),
    ],
  })

  allGroups.push({
    title: '变量赋值(局部)',
    items: oGraph.value!.locals.map(v => PNode.setterFromTGD(v))
  })

  allGroups.push({
    title: '变量赋值(项目/库)',
    items: [
      ...nodeFiltered.filter(n => n.type === PNodeType.Setter),
      ...oProject.value!.storage.globals.map(v => PNode.setterFromTGD(v)),
    ]
  })



  // 如果有pending槽
  // 1. 就需要确定是可以连接的
  // 2. 添加创建局部变量的按钮
  if (oBus.value.pendingSlot) {
    // const pslot = oBus.value.pendingSlot.prototype
    const pending = oBus.value.pendingSlot
    return allGroups.map(group => {
      return {
        ...group,
        items: group.items.filter(node => {
          return node.slots.find(s => s.compatibleWith(pending.prototype))
        })
      }
    }).filter(group => group.items.length !== 0)
  }
  else {
    return allGroups.filter(group => group.items.length !== 0)
  }

})



function getBgByDesc(desc: string) {
  const mapDescType = new Map<string, PNodeType>([
    ['操作符/纯函数', PNodeType.Expression],
    ['流程', PNodeType.Process],
    ['函数(项目)', PNodeType.Function],
    ['函数(库)', PNodeType.Function],
    ['变量(局部)', PNodeType.Getter],
    ['变量(项目/库)', PNodeType.Getter],
    ['变量赋值(局部)', PNodeType.Setter],
    ['变量赋值(项目/库)', PNodeType.Setter],
  ])
  return getBgByType(mapDescType.get(desc))
}

/** 内联变量创建 */
watch(
  () => JSON.parse(JSON.stringify(oGraph.value?.locals)),
  (val, oldVal) => {
    if (oldVal.length < val.length && base.value?.pinned) {
      const newLocal = lodash.last(oGraph.value?.locals)!
      
      let _node
      if (oBus.value.pendingSlot?.prototype.direction === 'in') {
        _node = PNode.getterFromTGD(newLocal)
      }
      else if (oBus.value.pendingSlot?.prototype.direction === 'out') {
        _node = PNode.setterFromTGD(newLocal)
      }
      else {
        throw new Error('pendingSlot缺失')
      }
      
      instantiateNode(_node, oBus.value.toLocal(position.value))
      base.value!.pinned = false
    }
  },
  {
    deep: true
  }
)

/** 内联函数创建 */
watch(
  () => JSON.parse(JSON.stringify(oStorage.value?.graphs)),
  (val, oldVal) => {
    if (oldVal.length < val.length && base.value?.pinned) {
      const newGraph = lodash.last(oStorage.value?.graphs)!
      
      const node = PNode.funcFromGraph(newGraph)
      
      instantiateNode(node, oBus.value.toLocal(position.value))
      base.value!.pinned = false
    }
  },
  {
    deep: true
  }
)

/** 结束内联编辑：局部变量 */
watch(() => oBus.value.isLocalEditShown, (val) => {
  if (!val) {
    base.value!.pinned = false
  }
})

/** 结束内联编辑：函数声明 */
watch(() => oBus.value.isParamEditShown, (val) => {
  if (!val) {
    base.value!.pinned = false
  }
})

/** 创建新的局部变量 */
function onNewLocal() {
  const pending = oBus.value.pendingSlot
  if (pending) {
    const type = pending.prototype.dataType!
    oBus.value.editLocal({
      name: '',
      type,
      initialValue: type.default
    })
  }
  else {
    oBus.value.editLocal()
  }
  
  base.value!.pinned = true
}

/** 创建新的函数 */
function onNewFunc() {

  const pending = oBus.value.pendingSlot
  if (pending) {
    const proto = pending.prototype
    const _mirror = proto.createMirror()
    const graph = new Graph()
    if (proto.direction === 'in') {
      graph.returns = [_mirror]
    }
    else {
      graph.params = [_mirror]
    }
    oBus.value.editParam(graph)
  }
  else {
    oBus.value.editParam()
  }
  
  base.value!.pinned = true
}
</script>

<style lang="scss" scoped>
.cnButton {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0.25rem;
}

.create-node-menu {
  width: 300px;
  height: 300px;
  background: var(--bg-body);
  box-shadow: var(--shadow-convex);
  padding: 2px;
}
</style>